Edit Mode UI and Logic Improvements

Here are the modifications to implement your requested improvements for the Edit Mode.

1. CSS Adjustments

Add the following styles to the <style> block to manage the layout of the palette and comment grid, ensuring they stay responsive and fully visible.

CSS

    /* --- Edit Mode UI Grid Styles --- */
   .edit-palette-grid {
       display: grid;
       grid-template-columns: repeat(4, 1fr);
       gap: 5px;
       width: 100%;
       padding: 5px;
       box-sizing: border-box;
   }

   .edit-comment-grid {
       display: flex;
       flex-wrap: wrap;
       justify-content: center;
       align-items: center;
       width: 100%;
       height: 100%;
       gap: 10px;
       padding: 10px;
       box-sizing: border-box;
       align-content: center;
   }

   .edit-palette-item {
       width: 100%;
       aspect-ratio: 1;
       cursor: pointer;
       box-sizing: border-box;
   }

   .edit-comment-item, .ejceespbmini-container {
       flex: 0 0 40%;
       max-width: 64px;
       aspect-ratio: 1;
       cursor: pointer;
       box-sizing: border-box;
       display: flex;
       justify-content: center;
       align-items: center;
   }

   /* Mobile specific adjustments */
   @media (max-width: 767px) {
       .ejceescomment {
           display: flex;
           flex-direction: column;
           justify-content: center;
       }
       
       /* Using an aspect-ratio media query to detect if .ejceescomment container would be wide/short */
       @media (min-aspect-ratio: 1/1) {
           .edit-comment-grid {
               flex-wrap: nowrap;
           }
           .edit-comment-item, .ejceespbmini-container {
               flex: 1 1 20%;
               max-width: 48px;
           }
       }
   }

2. Update getEditIconHTML

Modify this function to utilize the new classes and remove the inline borders.

JavaScript

        function getEditIconHTML(char, cssClass) {
           const tileId = pieceToHrefMap[char] || 'tile0';
           const tile0stroke = tileId === 'tile0' ? 'orange' : 'none';
           const tile0animtion = tileId === 'tile0' ? '<animateTransform attributeName="transform" attributeType="XML" type="translate" values="24,24;24,12;24,24;24,36;24,24" dur="3.6s" repeatCount="indefinite"/>' : '';
           const origTile = document.getElementById(tileId);
           const defsContent = origTile ? origTile.outerHTML : '';
           return `
               <svg class="${cssClass}" data-char="${char}" viewBox="0 0 48 48">
                   <defs>${defsContent}</defs>
                   <use id="edit-${char}" href="#${tileId}" fill="none" stroke="${tile0stroke}" transform="translate(24,24) scale(1,1) rotate(0)">${tile0animtion}</use>
               </svg>`;
       }

3. Update enterEditMode

Modify this function to implement the new grid layout, hide .ejceesstep, and display the initial long text tip.

JavaScript

        function enterEditMode() {
           isEditMode = true;
           preEditFEN = boardToFEN();
           editUndoStack = [preEditFEN];
           editRedoStack = [];
           isTile0Selected = true;
           selectedPalettePiece = null;
           editMiniBoardState = 0;
           editPickedPieceId = null;

           document.querySelector('.ejceesstep').style.display = 'none';
           setOutputText("Edit Mode: Select a piece to place or move.", false);

           if (pickedPieceId) setPickup(pickedPieceId, false);
           pickedPieceId = null;
           const pieces = etboard.querySelectorAll('use');
           pieces.forEach(el => {
               const href = el.getAttribute('href') || el.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
               if (/^#(tile([2-9]|1[0-5]))$/.test(href)) {
                   el.removeAttribute('currentmove');
                   el.setAttribute('stroke', 'none');
                   el.removeAttribute('stroke-width');
               }
           });
           const dot = document.getElementById('ejceesstartdot');
           if (dot) {
               dot.setAttribute('cx', initialDotCx);
               dot.setAttribute('cy', initialDotCy);
           }

           const recordContainer = document.querySelector('.ejceesrecord');
           const row1 = ['R', 'N', 'n', 'r', 'C', 'P', 'p', 'c', 'B', 'A', 'a', 'b'];
           recordContainer.innerHTML = '<div class="edit-palette-grid">' + row1.map(c => getEditIconHTML(c, 'edit-palette-item')).join('') + '</div>';

           const commentContainer = document.querySelector('.ejceescomment');
           let commentHTML = '<div class="edit-comment-grid">';
           commentHTML += getEditIconHTML('tile0', 'edit-comment-item');
           commentHTML += `<div class="ejceespbmini-container"></div>`;
           commentHTML += getEditIconHTML('K', 'edit-comment-item');
           commentHTML += getEditIconHTML('k', 'edit-comment-item');
           commentHTML += '</div>';
           commentContainer.innerHTML = commentHTML;

           const miniContainer = document.querySelector('.ejceespbmini-container');
           const cloneSVG = document.querySelector('.ejceespb').cloneNode(true);
           cloneSVG.setAttribute('class', 'ejceespbmini');
           cloneSVG.removeAttribute('id');
           cloneSVG.style.width = '100%';
           cloneSVG.style.height = '100%';
           miniContainer.appendChild(cloneSVG);
           updateMiniBoard();

           const btnDiv = document.querySelector('.ejceestextbtn');
           btnDiv.innerHTML = `
                   <div class="ejceestextbtninner">
                       <div class="note-btn btn-confirm" id="edit-confirm" title="Confirm">
                           <svg viewBox="0 0 24 24"><path fill="#4CAF50" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
                       </div>
                       <div class="note-btn btn-cancel" id="edit-cancel" title="Cancel">
                           <svg viewBox="0 0 24 24"><path fill="#F44336" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
                       </div>
                   </div>
               `;

           document.getElementById('edit-confirm').addEventListener('click', exitEditModeConfirm);
           document.getElementById('edit-cancel').addEventListener('click', exitEditModeCancel);

           bindEditUIEvents();
           updateEditUI();
           updateToolHighlights();
       }

4. Update bindEditUIEvents, updateEditUI and handleEditBoardClick

These functions have been updated to handle SVG stroke formatting, show short text prompts, and manage validation.

JavaScript

        function bindEditUIEvents() {
           document.querySelectorAll('.edit-palette-item').forEach(item => {
               item.addEventListener('click', () => {
                   if (item.classList.contains('disabled')) return;
                   const char = item.getAttribute('data-char');
                   selectedPalettePiece = (selectedPalettePiece === char) ? null : char;
                   if(selectedPalettePiece) setOutputText("Selected: " + selectedPalettePiece, false);
                   updateEditUI();
               });
           });

           document.querySelectorAll('.edit-comment-item').forEach(item => {
               item.addEventListener('click', () => {
                   const char = item.getAttribute('data-char');
                   if (char === 'tile0' || char === 'tile1') {
                       const newChar = char === 'tile0' ? 'tile1' : 'tile0';
                       item.setAttribute('data-char', newChar);
                       const useEl = item.querySelector('use');
                       if (useEl) {
                           useEl.setAttribute('href', `#${newChar}`);
                           useEl.setAttributeNS('http://www.w3.org/1999/xlink', 'href', `#${newChar}`);
                           if (newChar === 'tile0') {
                               useEl.innerHTML = '<animateTransform attributeName="transform" attributeType="XML" type="translate" values="24,24;24,12;24,24;24,36;24,24" dur="3.6s" repeatCount="indefinite"/>';
                           } else {
                               useEl.innerHTML = '<animateTransform attributeName="transform" attributeType="XML" type="rotate" values="0;180" calcMode="discrete" dur="3.6s" repeatCount="indefinite" additive="sum"/>';
                           }
                       }
                       isTile0Selected = !isTile0Selected;
                       setOutputText("Option changed.", true);
                   } else if (char === 'K' || char === 'k') {
                       currentTurn = (char === 'K') ? 'w' : 'b';
                       saveEditState();
                       setOutputText("Turn changed.", true);
                   }
                   updateEditUI();
               });
           });

           document.querySelector('.ejceespbmini-container').addEventListener('click', () => {
               editMiniBoardState = 1 - editMiniBoardState;
               const newFen = editMiniBoardState === 0 ? INITIAL_FEN : '4k4/9/9/9/9/9/9/9/9/4K4 w';
               const savedTurn = currentTurn;
               loadFEN(newFen);
               currentTurn = savedTurn;
               updateMiniBoard();
               saveEditState();
               updateEditUI();
               setOutputText("Mini board layout changed.", true);
           });
       }

       function updateEditUI() {
           const counts = {};
           for (const id of tileMap.values()) counts[id[0]] = (counts[id[0]] || 0) + 1;

           document.querySelectorAll('.edit-palette-item').forEach(item => {
               const char = item.getAttribute('data-char');
               const max = (char === 'p' || char === 'P') ? 5 : 2;
               if ((counts[char] || 0) >= max) {
                   item.style.opacity = '0.36';
                   item.classList.add('disabled');
                   if (selectedPalettePiece === char) selectedPalettePiece = null;
               } else {
                   item.style.opacity = '1';
                   item.classList.remove('disabled');
               }
               
               const useEl = item.querySelector('use');
               if (useEl) {
                   if (selectedPalettePiece === char) {
                       useEl.setAttribute('stroke', 'white');
                       useEl.setAttribute('stroke-width', '2');
                   } else {
                       useEl.setAttribute('stroke', 'none');
                   }
               }
           });

           document.querySelectorAll('.edit-comment-item').forEach(item => {
               const char = item.getAttribute('data-char');
               const useEl = item.querySelector('use');
               
               if (char === 'tile0' || char === 'tile1') {
                   if (isTile0Selected) {
                       useEl.setAttribute('stroke', 'white');
                       useEl.setAttribute('stroke-width', '2');
                   } else {
                       useEl.setAttribute('stroke', 'orange');
                       useEl.setAttribute('stroke-width', '1');
                   }
               } else if (char === 'K' || char === 'k') {
                   item.style.opacity = ((char === 'K' && currentTurn === 'w') || (char === 'k' && currentTurn === 'b')) ? '1' : '0.36';
                   if ((char === 'K' && currentTurn === 'w') || (char === 'k' && currentTurn === 'b')) {
                       useEl.setAttribute('stroke', 'white');
                       useEl.setAttribute('stroke-width', '2');
                   } else {
                       useEl.setAttribute('stroke', 'none');
                   }
               }
           });
       }

       function handleEditBoardClick(x, y, clickedId) {
           if (!isTile0Selected) {
               if (!selectedPalettePiece) {
                   if (clickedId && clickedId[0] !== 'K' && clickedId[0] !== 'k') {
                       removeEditPiece(clickedId);
                       saveEditState();
                       updateEditUI();
                       setOutputText("Removed piece.", true);
                   }
               } else {
                   if (clickedId) {
                       if (clickedId[0] !== 'K' && clickedId[0] !== 'k' && isValidEditPlacement(selectedPalettePiece, x, y)) {
                           removeEditPiece(clickedId);
                           placeEditPiece(selectedPalettePiece, x, y);
                           saveEditState();
                           updateEditUI();
                           setOutputText("Replaced piece.", true);
                       }
                   } else {
                       if (isValidEditPlacement(selectedPalettePiece, x, y)) {
                           placeEditPiece(selectedPalettePiece, x, y);
                           saveEditState();
                           updateEditUI();
                           setOutputText("Placed piece.", true);
                       }
                   }
               }
           } else {
               if (editPickedPieceId) {
                   if (clickedId === editPickedPieceId) {
                       setPickup(editPickedPieceId, false);
                       editPickedPieceId = null;
                       setOutputText("Edit Mode: Select a piece to place or move.", false);
                   } else if (clickedId) {
                       setPickup(editPickedPieceId, false);
                       editPickedPieceId = clickedId;
                       setPickup(editPickedPieceId, true);
                       setOutputText("Selected: " + editPickedPieceId, false);
                   } else {
                       if (isValidEditPlacement(editPickedPieceId[0], x, y)) {
                           const pos = piecePos.get(editPickedPieceId);
                           tileMap.delete(`${pos.x},${pos.y}`);
                           tileMap.set(`${x},${y}`, editPickedPieceId);
                           piecePos.set(editPickedPieceId, { x, y });
                           const pieceEl = document.getElementById(editPickedPieceId);
                           const vis = getVisualCoords(x, y);
                           pieceEl.setAttribute('transform', `translate(${vis.x*48+24}, ${vis.y*48+24})`);
                           setPickup(editPickedPieceId, false);
                           editPickedPieceId = null;
                           saveEditState();
                           updateEditUI();
                           setOutputText("Moved piece.", true);
                       }
                   }
               } else {
                   if (clickedId) {
                       editPickedPieceId = clickedId;
                       setPickup(editPickedPieceId, true);
                       setOutputText("Selected: " + editPickedPieceId, false);
                   } else {
                       if (selectedPalettePiece && isValidEditPlacement(selectedPalettePiece, x, y)) {
                           placeEditPiece(selectedPalettePiece, x, y);
                           saveEditState();
                           updateEditUI();
                           setOutputText("Placed piece.", true);
                       }
                   }
               }
           }
       }

5. Update updateMiniBoard & Add rotation events

Ensure the mini board elements respect the coordinates translation of getVisualCoords().

JavaScript

        function updateMiniBoard() {
           const miniBoard = document.querySelector('.ejceespbmini');
           if (!miniBoard) return;
           const targetFen = editMiniBoardState === 0 ? '4k4/9/9/9/9/9/9/9/9/4K4' : INITIAL_FEN.split(' ')[0];

           const pieces = miniBoard.querySelectorAll('use');
           pieces.forEach(el => {
               const href = el.getAttribute('href') || el.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
               if (/^#(tile([2-9]|1[0-5]))$/.test(href)) el.remove();
           });

           const etboardMini = miniBoard.querySelector('.etboard') || miniBoard.querySelector('g:last-child');
           targetFen.split('/').forEach((row, y) => {
               let x = 0;
               for (let i = 0; i < row.length; i++) {
                   const char = row[i];
                   if (/[0-9]/.test(char)) x += parseInt(char, 10);
                   else {
                       const useEl = document.createElementNS('http://www.w3.org/2000/svg', 'use');
                       useEl.setAttribute('href', `#${pieceToHrefMap[char]}`);
                       const vis = getVisualCoords(x, y); // Hook up rotation/flip sync here
                       useEl.setAttribute('transform', `translate(${vis.x * 48 + 24}, ${vis.y * 48 + 24})`);
                       etboardMini.appendChild(useEl);
                       x++;
                   }
               }
           });
       }

Note: Look for document.getElementById('tool-rotate').addEventListener and tool-flip and add the following line just before their saveStateToUndo();:

JavaScript

            if (isEditMode) updateMiniBoard();

6. Update isValidEditPlacement and exitEditMode Logic

Added proper pawn constraints based on standard positioning arrays, and robust validation upon completing board setup.

JavaScript

        function isValidEditPlacement(char, x, y) {
           if (char === 'K') return x >= 3 && x <= 5 && y >= 7 && y <= 9;
           if (char === 'k') return x >= 3 && x <= 5 && y >= 0 && y <= 2;
           if (char === 'A') return x >= 3 && x <= 5 && y >= 7 && y <= 9 && Math.abs(x - 4) === Math.abs(y - 8);
           if (char === 'a') return x >= 3 && x <= 5 && y >= 0 && y <= 2 && Math.abs(x - 4) === Math.abs(y - 1);
           if (char === 'B') return y >= 5 && y <= 9 && y % 2 !== 0 && ((y === 5 || y === 9) ? (x === 2 || x === 6) : (x === 0 || x === 4 || x === 8));
           if (char === 'b') return y >= 0 && y <= 4 && y % 2 === 0 && ((y === 0 || y === 4) ? (x === 2 || x === 6) : (x === 0 || x === 4 || x === 8));
           if (char === 'P') {
               if (y > 6) return false;
               if ((y === 5 || y === 6) && x % 2 !== 0) return false;
           }
           if (char === 'p') {
               if (y < 3) return false;
               if ((y === 3 || y === 4) && x % 2 !== 0) return false;
           }
           return true;
       }

       function exitEditModeCancel() {
           isEditMode = false;
           document.querySelector('.ejceesstep').style.display = 'flex';
           loadFEN(preEditFEN);
           renderRecordUI();
           renderNoteUI();
           updateToolHighlights();
           restoreOutputText();
       }

       function exitEditModeConfirm() {
           // Check Validation Rules
           let rk = null, bk = null;
           for (const [pos, id] of tileMap.entries()) {
               if (id[0] === 'K') rk = pos;
               if (id[0] === 'k') bk = pos;
           }
           if (!rk || !bk) {
               alert("Both Kings must be present.");
               setOutputText("Validation failed: Missing King.", true);
               return;
           }

           if (checkKingsFacing(tileMap)) {
               alert("Invalid state: Kings are facing each other.");
               setOutputText("Validation failed: Kings facing.", true);
               return;
           }

           const isRedTurn = currentTurn === 'w';
           const oppKingPos = isRedTurn ? bk : rk;
           const [okX, okY] = oppKingPos.split(',').map(Number);
           if (isSquareAttacked(okX, okY, !isRedTurn, tileMap)) {
               alert("Invalid state: Active player can directly capture the opponent's King.");
               setOutputText("Validation failed: Opponent King in check.", true);
               return;
           }

           let hasValidMove = false;
           for (const [pos, id] of tileMap.entries()) {
               const pieceIsRed = id[0] === id[0].toUpperCase();
               if (pieceIsRed === isRedTurn) {
                   const [sx, sy] = pos.split(',').map(Number);
                   for (let tx = 0; tx <= 8; tx++) {
                       for (let ty = 0; ty <= 9; ty++) {
                           if (sx === tx && sy === ty) continue;
                           const targetId = tileMap.get(`${tx},${ty}`);
                           if (targetId && isSameTeam(id, targetId)) continue;
                           if (isValidMove(id, sx, sy, tx, ty)) {
                               hasValidMove = true;
                               break;
                           }
                       }
                       if (hasValidMove) break;
                   }
               }
               if (hasValidMove) break;
           }
           if (!hasValidMove) {
               alert("Invalid state: Active player is stalemated (no valid moves).");
               setOutputText("Validation failed: Stalemated.", true);
               return;
           }

           isEditMode = false;
           document.querySelector('.ejceesstep').style.display = 'flex';
           halfMoveClock = 0;
           fullMoveNumber = 1;
           const finalFEN = boardToFEN();
           historyFEN = {
               fen: finalFEN,
               move: null,
               lastMove: null,
               c: "",
               v: []
           };
           currentBranch = [];
           currentStepIndex = 0;
           stepSlider.max = 0;
           stepSlider.value = 0;
           saveStateToUndo();
           renderRecordUI();
           renderNoteUI();
           updateToolHighlights();
           restoreOutputText();
       }